/****************************************************************************
 * Copyright 2009 Huascar A. Sanchez                                        *
 *                                                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");          *
 * you may not use this file except in compliance with the License.         *
 * You may obtain a copy of the License at                                  *
 *                                                                          *
 *     http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                          *
 * Unless required by applicable law or agreed to in writing, software      *
 * distributed under the License is distributed on an "AS IS" BASIS,        *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
 * See the License for the specific language governing permissions and      *
 * limitations under the License.                                           *
 ****************************************************************************/
package com.gotobject.filter.common;

import com.gotobject.filter.predicate.AbstractPredicate;
import com.gotobject.filter.predicate.Predicate;
import com.gotobject.filter.spi.ExecutionContext;
import com.gotobject.filter.spi.Function;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import static com.gotobject.filter.common.Iterables.asList;
import static com.gotobject.filter.common.Iterables.capturedWildCard;

/**
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 */
public class Predicates {
    public static final Predicate<Object> ALL =  new AbstractPredicate<Object>(){
        @Override
        public boolean apply(Object thing) {
            return true;
        }

        @Override
        public String toString() {
            return "all()";
        }
    };

    private Predicates(){}

    public static <T> Predicate<T> all(){
		//noinspection unchecked
		return ExecutionContext.any((Predicate<T>) ALL);// unchecked warning - but u and I know that this is typesafe
    }

    public static <A, B> Predicate<A> compose(Predicate<B> predicate, Function<A, ? extends B> function) {
        return ExecutionContext.any(
                new CompositionPredicate<A, B>(
                        predicate,
                        function
                )
        );
    }

    public static <T> Predicate<T> conj(final Predicate<T> left, final Predicate<T> right){
		return ExecutionContext.any(new AbstractPredicate<T>(){
			private final Predicate<T> whole = left.and(right);
			@Override
			public boolean apply(T thing) {
				return whole.apply(thing);
			}


			@Override
			public String toString() {
				return whole.toString();
			}
		});
    }


	public static <T> Predicate<T> contains(final Iterable <? extends T> key) {
		return ExecutionContext.any(new AbstractPredicate<T>(){
			private final List<T> subject = asList(capturedWildCard(key));
			@Override
			public boolean apply(T thing) {
				return subject.contains(thing);
			}


			@Override
			public String toString() {
				return "contains(" + subject + ")";
			}
		});
	}


    public static <T> Predicate<T> eq(final T elem){
        return ExecutionContext.any(new AbstractPredicate<T>(){
            @SuppressWarnings({"ObjectEquality"})
            @Override
            public boolean apply(T thing) {
                return thing == elem || thing.equals(elem);
            }

            @Override
            public String toString() {
                return "eq(" + elem + ")";
            }
        });
    }

    public static <T> Predicate<T> eq(final String key, final Object value){
        return ExecutionContext.any(new AbstractPredicate<T>(){
            @Override
            public boolean apply(T thing) {
                try {
                    // ugly reflection call, but what can I do? :(
                    final Field target = thing.getClass().getDeclaredField(key);
                    target.setAccessible(true);
                    return eq(target.get(thing)).apply(value);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public String toString() {
                return "eq(" + key + "=" + value + ")";
            }
        });
    }

    public static <T> Predicate<T> endsWith(final String... value){
        return ExecutionContext.any(new AbstractPredicate<T>(){
            @Override
            public boolean apply(T thing) {
                boolean result = false;
                for(String each : value){
                    if(thing.toString().endsWith(each)){
                        result = true;
                    }
                }
                return result;
            }

            @Override
            public String toString() {
                return "endsWith(" + Arrays.asList(value) + ")";
            }
        });
    }

    public static <T> Predicate<T> disj(final Predicate<T> left, final Predicate<T> right){
		return ExecutionContext.any(new AbstractPredicate<T>(){
			private final Predicate<T> whole = left.or(right);
			@Override
			public boolean apply(T thing) {
				return whole.apply(thing);
			}


			@Override
			public String toString() {
				return whole.toString();
			}
		});
    }

    public static Predicate<Integer> gt(final int number){
        return ExecutionContext.any(new AbstractPredicate<Integer>(){
            @Override
            public boolean apply(Integer thing) {
                return thing > number;
            }

            @Override
            public String toString() {
                return "gt(" + number + ")";
            }
        });
    }

    public static Predicate<Integer> lt(final int number){
        return ExecutionContext.any(new AbstractPredicate<Integer>(){
            @Override
            public boolean apply(Integer thing) {
                return thing < number;
            }

            @Override
            public String toString() {
                return "lt(" + number + ")";
            }
        });
    }

    public static Predicate<Integer> mod(final int number){
        return ExecutionContext.any(new AbstractPredicate<Integer>(){
            @Override
            public boolean apply(Integer thing) {
                return (thing % number) == 0;
            }

            @Override
            public String toString() {
                return "mod(" + number + ")";
            }
        });
    }

    public static <T> Predicate<T> not(final Predicate<? super T> predicate) {
        return ExecutionContext.any(new AbstractPredicate<T>(){
            @Override
            public boolean apply(T thing) {
                return !predicate.apply(thing);
            }

            @Override
            public String toString() {
                return "not(" + predicate + ")";
            }
        });
    }

    public static <T> Predicate<T> skip(final int number){
        return new AbstractPredicate<T>(){
            private final AtomicInteger counter = new AtomicInteger(0);
            @Override
            public boolean apply(T thing) {
                return counter.incrementAndGet() > number;
            }

            @Override
            public String toString() {
                return "skip(" + number + ")";
            }
        };
    }

    public static <T> Predicate<T> startsWith(final String... value){
		return ExecutionContext.any(new AbstractPredicate<T>(){
            @Override
            public boolean apply(T thing) {
                boolean result = false;
                for(String each : value){
                    if(thing.toString().contains(each)){
                        result = true;
                    }
                }
                return result;
            }

            @Override
            public String toString() {
                return "startsWith(" + Arrays.asList(value) + ")";
            }
        });
    }

    public static <T> Predicate<T> take(final int number){
		return new AbstractPredicate<T>(){
			private final AtomicInteger counter = new AtomicInteger(0);
            @Override
            public boolean apply(T thing) {
                counter.incrementAndGet();
                return counter.get() <= number;
            }

            @Override
            public String toString() {
                return "take(" + number + ")";
            }
		};
    }

    public static <T> Predicate<T> between(final T a, final T b){
        return ExecutionContext.any(new AbstractPredicate<T>(){
            private final List<T> container = new ArrayList<T>();
            @Override
            public boolean apply(T thing) {
                if(eq(a).apply(thing)){
                    container.add(thing);
                }

                if(eq(b).apply(thing)){
                    container.add(thing);
                }

                return (thing.equals(b))
                        || !((container.contains(a)
                        || !container.contains(b))
                        && (!container.contains(a)
                        || container.contains(b)));
            }

            @Override
            public String toString() {
                return "between(" + a + " and " + b + ")";
            }
		});
    }

    public static <T> Predicate<T> annotatedWith(final Class<? extends Annotation> annoKlass){
        return ExecutionContext.any(new AbstractPredicate<T>(){
            @Override
            public boolean apply(T thing) {
                return containsAnnotation(thing, annoKlass);
            }

            @Override
            public String toString() {
                return "annotatedWith(" + annoKlass + ")";
            }
        });
    }

    public static <T> Predicate<T> subClassOf(final Class<?> klass){
        return ExecutionContext.any(
            new AbstractPredicate<T>(){
                        @Override
                        public boolean apply(T thing) {
                            return !klass.isInterface() && klass.isAssignableFrom(thing.getClass());
                        }

                        @Override
                        public String toString() {
                            return "subClassOf(" + klass + ")";
                        }
                    }
        );
    }

    public static <T> Predicate<T> typeOf(final Class<?> interfaze){
        return ExecutionContext.any(
            new AbstractPredicate<T>(){
                @Override
                public boolean apply(T thing) {
                    return interfaze.isInterface() && interfaze.isAssignableFrom(thing.getClass());
                }

                @Override
                public String toString() {
                    return "typeOf(" + interfaze + ")";
                }
            }
        );
    }

    public static <T> boolean containsAnnotation(T thing, Class<? extends Annotation> annoKlass){
        return (thing instanceof Method)
                ? Method.class.cast(thing).isAnnotationPresent(annoKlass)
                : (thing instanceof Field)
                ? Field.class.cast(thing).isAnnotationPresent(annoKlass)
                : (thing instanceof Class<?>) ? Class.class.cast(thing).isAnnotationPresent(annoKlass)
                : (thing instanceof Constructor) && Constructor.class.cast(thing).isAnnotationPresent(annoKlass);
    }

    public static <T> Predicate<T> modifiedBy(final int mods){
        return ExecutionContext.any(new AbstractPredicate<T>(){
            @Override
            public boolean apply(T thing) {
                return equalsModifier(thing, mods);
            }

            @Override
            public String toString() {
                return "modifiedBy(" + mods + ")";
            }
        });
    }

    public static <T> boolean equalsModifier(T thing, int mods){
                return (thing instanceof Method)
                        ? Method.class.cast(thing).getModifiers() == mods
                        : (thing instanceof Field)
                        ? Field.class.cast(thing).getModifiers() == mods
                        : (thing instanceof Class<?>) ? Class.class.cast(thing).getModifiers() == mods
                        : (thing instanceof Constructor) && Constructor.class.cast(thing).getModifiers() == mods;
    }

    public static Predicate<Element> attribute(final String name, final String value){
        return ExecutionContext.select(new AbstractPredicate<Element>(){
            @Override
            public boolean apply(Element thing) {
                return thing.hasAttribute(name) && thing.getAttribute(name).equals(value);  //tocode
            }

            @Override
            public String toString() {
                return "attribute(name=" + name + ", value=" + value + ")" ;
            }
        });
    }

    public static Predicate<Node> valuedWith(final String key){
        return ExecutionContext.select(new AbstractPredicate<Node>(){
            @Override
            public boolean apply(Node thing) {
                return thing.getNodeValue().equals(key);
            }

            @Override
            public String toString() {
                return "valuedWith(" + key + ")";
            }
        });
    }


  /** @see Predicates#compose(Predicate, Function) */
  private static class CompositionPredicate<A, B> extends AbstractPredicate<A> {
      final Predicate<B> p;
      final Function<A, ? extends B> f;


      CompositionPredicate(Predicate<B> p, Function<A, ? extends B> f){
          this.p = p;
          this.f = f;
      }

      @Override
      public boolean apply(A thing) {
          try {
              return p.apply(f.apply(thing));
          } catch (Exception e){
              return false;
          }
      }
  }
}
